package com.jkdroid.maya.view;

import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Message;
import android.support.annotation.AnimRes;
import android.support.annotation.IntDef;
import android.support.annotation.LayoutRes;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.Toast;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.SoftReference;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * 悬浮框类,toast原理,可支持MIUI
 *
 * @author guangzhihao
 * @since 2016/7/25
 */
public class FlowView {

    private static final String TAG = "FlowView";
    public static final int DURATION_ALWAYS = 0;
    private boolean isShow = false;
    private final Builder mBuilder;

    private static final int BORDER_BASE = 1;
    public static final int BORDER_LEFT = BORDER_BASE << 1;
    public static final int BORDER_TOP = BORDER_BASE << 2;
    public static final int BORDER_RIGHT = BORDER_BASE << 3;
    public static final int BORDER_BOTTOM = BORDER_BASE << 4;

    public static final int ACTION_SHOW = 0;
    public static final int ACTION_MOVE = 1;
    public static final int ACTION_UP = 2;
    public static final int ACTION_FLING = 3;

    @IntDef({FLING_TYPE_NONE, FLING_TYPE_STOP_ON_EDGE, FLING_TYPE_BOUNCE})
    @Retention(RetentionPolicy.SOURCE)
    public @interface FlyingType {
    }
    public static final int FLING_TYPE_NONE = 0;
    public static final int FLING_TYPE_STOP_ON_EDGE = 1;
    public static final int FLING_TYPE_BOUNCE = 2;

    private FlowView(Builder builder) {
        this.mBuilder = builder;
    }

    public static abstract class OnFlowListener {
        public abstract void onMove(int downX, int downY, int curX, int curY);

        public abstract void onDown(int downX, int downY);

        public abstract void onUp(int upX, int upY);

        public void onConfigurationChanged(Configuration newConfig) {
        }

        public void onAttachedToWindow() {
        }
    }

    public interface OnBorderedListener {
        void onEdge(boolean changed, int border, int action);
    }

    private static class FyingHandler extends android.os.Handler {
        private final SoftReference<Builder> mBuilderSoftReference;
        int vX = 0;
        int vY = 0;
        int aX = 0;
        int aY = 0;
        int interval = 1000 / 40;

        private FyingHandler(Builder builder) {
            mBuilderSoftReference = new SoftReference<>(builder);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Builder builder = mBuilderSoftReference.get();
            if (builder == null) {
                return;
            }
            switch (msg.what) {
                case Builder.WHAT_START:
                    removeMessages(Builder.WHAT_START);
                    vX = msg.arg1;
                    vY = msg.arg2;
                    aX = 2 * vX * -1;
                    aY = 2 * vY * -1;
                    sendEmptyMessageDelayed(Builder.WHAT_MOVE, interval);
                    break;
                case Builder.WHAT_MOVE:
                    removeMessages(Builder.WHAT_MOVE);
                    int dX;
                    int dY;
                    if (Math.abs(vX) >= Math.abs(aX * this.interval / 1000)) {
                        //够减
                        dX = vX * interval / 1000 + 1 / 2 * aX * interval / 1000 * interval / 1000;
                        vX = vX + aX * interval / 1000;
                    } else {
                        //不够减
                        dX = vX * vX / 2 / aX;
                        vX = 0;
                    }
                    if (Math.abs(vY) >= Math.abs(aY * this.interval / 1000)) {
                        //够减
                        dY = vY * interval / 1000 + 1 / 2 * interval / 1000 * aY * interval / 1000;
                        vY = vY + aY * interval / 1000;
                    } else {
                        //不够减
                        dY = vY * vY / 2 / aY;
                        vY = 0;
                    }
                    if (dX != 0 || dY != 0) {
                        builder.params.x += dX;
                        builder.params.y += dY;
                        int type = builder.getBorderType();
                        builder.params = builder.amendParams(builder.params, type);
                        builder.mAction = ACTION_FLING;
                        builder.updateViewPosition(builder.params);
                        if (type != 0) {
                            if (builder.mFlingType == FLING_TYPE_STOP_ON_EDGE) {
                                //停止
                                vX = 0;
                                vY = 0;
                            } else if (builder.mFlingType == FLING_TYPE_BOUNCE) {
                                //反弹
                                if ((type & BORDER_LEFT) > 0 || (type & BORDER_RIGHT) > 0) {
                                    vX = vX * -1 / 2;
                                    aX *= -2;
                                }
                                if ((type & BORDER_TOP) > 0 || (type & BORDER_BOTTOM) > 0) {
                                    vY = vY * -1 / 2;
                                    aY *= -2;
                                }
                            }
                        }
                        sendEmptyMessageDelayed(builder.WHAT_MOVE, this.interval);
                    }
                    break;
                default:
                    break;
            }
            builder = null;
        }
    }

    public static class Builder {
        protected static final int WHAT_MOVE = 1;
        protected static final int WHAT_START = 0;
        private FlowView mFlowView;
        private final Toast toast;
        private OnFlowListener mOnFlowListener;
        private View mView;
        private FrameLayout mRootView;
        private Context mContext;
        private final Point mTouchPoint;
        private final Point mDownPoint;
        private int paramX;
        private WindowManager.LayoutParams params;
        private int paramY;
        private WindowManager mWM;
        private int animations = -1;
        private final Point mLastPosition = new Point(-1, -1);

        private Object mTN;
        private Method show;
        private Method hide;

        private float mAlpha = 1;
        private int mAction = ACTION_SHOW;
        private int mDuration = DURATION_ALWAYS;
        private int mScreenSizeWidth;
        private int mScreenSizeHeight;
        private int mMarginX;
        private int mMarginY;
        private int mGravity = Gravity.LEFT | Gravity.TOP;
        private OnBorderedListener mOnBorderedListener;
        private int mStatusBarHeight;
        private int mLastBorder = 0;
        private int mPreBorder = 0;
        private GestureDetector mGestureDetector;
        private int mFlingType = FLING_TYPE_NONE;
        private final FyingHandler mHandler;
        private boolean mInterceptTouch = false;
        private boolean mDrawable = true;


        private Builder(Context context) {
            if (context == null) {
                throw new IllegalArgumentException("Context can not be  null");
            }
            this.mContext = context instanceof Activity ? context.getApplicationContext() : context;
            toast = new Toast(mContext);
            mRootView = new FrameLayout(context) {

                @Override
                public boolean onInterceptTouchEvent(MotionEvent ev) {
                    return mInterceptTouch;
                }

                @Override
                public boolean onTouchEvent(MotionEvent event) {
                    return Builder.this.onTouchEvent(this, event);
                }

                @Override
                protected void onAttachedToWindow() {
                    super.onAttachedToWindow();
                    if (mOnFlowListener != null) {
                        mOnFlowListener.onAttachedToWindow();
                    }
                }

                @Override
                protected void onConfigurationChanged(Configuration newConfig) {
                    super.onConfigurationChanged(newConfig);
                    if (mOnFlowListener != null) {
                        mOnFlowListener.onConfigurationChanged(newConfig);
                    }
                }

            };
            mGestureDetector = new GestureDetector(mContext, new GestureDetector.SimpleOnGestureListener() {
                @Override
                public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
                    if (mDrawable && mFlingType != FLING_TYPE_NONE && (Math.abs(velocityX) > 500 || Math.abs(velocityY) > 500)) {
                        //开启惯性运动
                        startFreeFall(velocityX, velocityY);
                    }
                    return false;
                }
            });
            mHandler = new FyingHandler(this);
            mTouchPoint = new Point();
            mDownPoint = new Point();
            toast.setView(mRootView);
        }

        private void startFreeFall(float velocityX, float velocityY) {
            //获取当前的x,y,根据阻尼进行不断刷新,只有0.5秒的惯性.
            mHandler.removeCallbacksAndMessages(null);
            Message.obtain(mHandler, WHAT_START, (int) velocityX, (int) velocityY).sendToTarget();
        }

        private void cancelFreeFall() {
            mHandler.removeCallbacksAndMessages(null);
        }

        public Builder setOnFlowListener(OnFlowListener listener) {
            this.mOnFlowListener = listener;
            return this;
        }

        public Builder setOnBorderedListener(OnBorderedListener listener) {
            this.mOnBorderedListener = listener;
            return this;
        }

        public Builder setFlingType(@FlyingType int type) {
            this.mFlingType = type;
            return this;
        }

        public Builder setView(View view) {
            mView = view;
            mRootView.removeAllViews();
            mRootView.addView(mView);
            return this;
        }

        public Builder setView(@LayoutRes int id) {
            return setView(View.inflate(mContext, id, null));
        }

        public Builder setAnimations(@AnimRes int animations) {
            this.animations = animations;
            return this;
        }

        public Builder setDuration(int duration) {
            this.mDuration = duration;
            return this;
        }

        public Builder setPosition(int x, int y) {
            mLastPosition.x = x < 0 ? 0 : (x > mScreenSizeWidth ? mScreenSizeWidth : x);
            mLastPosition.y = y < 0 ? 0 : (y > mScreenSizeHeight ? mScreenSizeHeight : y);
            mGravity = Gravity.NO_GRAVITY;
            return this;
        }

        /**
         * 和setPosition哪个最后调用哪个最后生效
         *
         * @param gravity
         * @param offsetX
         * @param offsetY
         * @return
         */
        public Builder setGravity(int gravity, int offsetX, int offsetY) {
            this.mGravity = gravity;
            this.mMarginX = offsetX;
            this.mMarginY = offsetY;
            return this;
        }

        public int getScreenWidth() {
            return mScreenSizeWidth;
        }

        public int getScreenHeight() {
            return mScreenSizeHeight;
        }

        public FlowView build() {
            mFlowView = new FlowView(this);
            return mFlowView;
        }

        private boolean onTouchEvent(View v, MotionEvent event) {
            if (mDrawable){
                mTouchPoint.x = (int) event.getRawX();
                mTouchPoint.y = (int) event.getRawY();
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:    //捕获手指触摸按下动作
                        //获取相对View的坐标，即以此View左上角为原点
                        mDownPoint.x = mTouchPoint.x;
                        mDownPoint.y = mTouchPoint.y;
                        paramX = params.x;
                        paramY = params.y;
                        onDown(mDownPoint.x, mDownPoint.y);
                        break;
                    case MotionEvent.ACTION_MOVE:   //捕获手指触摸移动动作
                        mAction = ACTION_MOVE;
                        onMove(mDownPoint.x, mDownPoint.y, mTouchPoint.x, mTouchPoint.y);
                        break;
                    case MotionEvent.ACTION_CANCEL:
                    case MotionEvent.ACTION_UP:    //捕获手指触摸离开动作
                        mAction = ACTION_UP;
                        onUp(mTouchPoint.x, mTouchPoint.y);
                        break;
                }
            }
            return mGestureDetector.onTouchEvent(event);
        }

        private int getBorderType() {
            int type = 0;
            type += params.x <= 0 ? BORDER_LEFT : (params.x >= mScreenSizeWidth - mRootView.getWidth() ? BORDER_RIGHT : 0);
            type += params.y <= 0 ? BORDER_TOP : (params.y >= mScreenSizeHeight - mRootView.getHeight() - getStatusBarHeight() ? BORDER_BOTTOM : 0);
            return type;
        }

        private WindowManager.LayoutParams amendParams(WindowManager.LayoutParams params, int type) {
            if (type == 0) {
                return params;
            }
            if ((type & BORDER_LEFT) > 0) {
                params.x = 0;
            } else if ((type & BORDER_RIGHT) > 0) {
                params.x = mScreenSizeWidth - mRootView.getWidth();
            }
            if ((type & BORDER_TOP) > 0) {
                params.y = 0;
            } else if ((type & BORDER_BOTTOM) > 0) {
                params.y = mScreenSizeHeight - mRootView.getHeight() - getStatusBarHeight();
            }
            return params;
        }

        /**
         * @param upX x抬起点
         * @param upY y抬起点
         */
        protected void onUp(int upX, int upY) {
            if (mOnFlowListener != null) {
                mOnFlowListener.onUp(upX, upY);
            }
        }

        /**
         * @param downX down下的x
         * @param downY down下的y
         * @param curX  当前的x
         * @param curY  当前的y
         */
        protected void onMove(int downX, int downY, int curX, int curY) {
            params.x = paramX + curX - downX;
            params.y = paramY + curY - downY;
            params = amendParams(params, getBorderType());
            updateViewPosition(params);
            if (mOnFlowListener != null) {
                mOnFlowListener.onMove(downX, downY, curX, curY);
            }
        }

        /**
         * @param downX x落点
         * @param downY y落点
         */
        protected void onDown(int downX, int downY) {
            if (mOnFlowListener != null) {
                mOnFlowListener.onDown(downX, downY);
            }
        }

        protected void updateViewPosition(WindowManager.LayoutParams params) {

            //判断是否贴边
            mPreBorder = mLastBorder;
            mLastBorder = getBorderType();
            if (mOnBorderedListener != null) {
                mOnBorderedListener.onEdge(mLastBorder != mPreBorder, mLastBorder, mAction);
            }
            //更新浮动窗口位置参数
            mWM.updateViewLayout(toast.getView(), params);  //刷新显示
        }

        public Builder setAlpha(float alpha) {
            mAlpha = alpha;
            if (params != null) {
                params.alpha = alpha;
            }
            return this;
        }

        private void initTN() {
            try {
                Field tnField = toast.getClass().getDeclaredField("mTN");
                tnField.setAccessible(true);
                mTN = tnField.get(toast);
                show = mTN.getClass().getMethod("show");
                hide = mTN.getClass().getMethod("hide");

                Field tnParamsField = mTN.getClass().getDeclaredField("mParams");
                tnParamsField.setAccessible(true);
                params = (WindowManager.LayoutParams) tnParamsField.get(mTN);
                params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;

                /**设置动画*/
                if (animations != -1) {
                    params.windowAnimations = animations;
                }

                /**调用tn.show()之前一定要先设置mNextView*/
                Field tnNextViewField = mTN.getClass().getDeclaredField("mNextView");
                tnNextViewField.setAccessible(true);
                tnNextViewField.set(mTN, toast.getView());
            } catch (Exception e) {
                e.printStackTrace();
            }
            toast.setGravity(Gravity.LEFT | Gravity.TOP, 0, 0);
        }

        private void saveCurPosition() {
            mLastPosition.x = params.x;
            mLastPosition.y = params.y;
            mGravity = Gravity.NO_GRAVITY;
        }

        private void restorePosition() {
            mPreBorder = mLastBorder = 0;
            params.alpha = mAlpha;
            mWM = (WindowManager) mContext.getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
            loadScreenSize();
            if (mGravity == Gravity.NO_GRAVITY) {
                params.x = mLastPosition.x;
                params.y = mLastPosition.y;
            } else {
                Rect rect = new Rect();
                Gravity.apply(mGravity, mRootView.getWidth(), mRootView.getHeight(), new Rect(0, 0, mScreenSizeWidth + 1, mScreenSizeHeight + 1), rect);

                int xG = mGravity & 15;//0x00001111,取低位
                int yG = mGravity & 240;//0x11110000,取高位
                int diffX = (xG == Gravity.LEFT) ? Math.abs(mMarginX) : (xG == Gravity.RIGHT) ? -Math.abs(mMarginX) : mMarginX;
                int diffY = (yG == Gravity.TOP) ? Math.abs(mMarginY) : (yG == Gravity.BOTTOM) ? -Math.abs(mMarginY) - getStatusBarHeight() : mMarginY;

                params.x = rect.left + diffX;
                params.y = rect.top + diffY;
                mGravity = Gravity.NO_GRAVITY;
            }
            updateViewPosition(params);
        }

        private void requestRefresh() {
            params.alpha = mAlpha;
            updateViewPosition(params);
        }

        private void loadScreenSize() {
            if (mScreenSizeWidth == 0) {
                DisplayMetrics metrics = new DisplayMetrics();
                mWM.getDefaultDisplay().getMetrics(metrics);
                mScreenSizeWidth = metrics.widthPixels;
                mScreenSizeHeight = metrics.heightPixels;
                Log.i("屏幕", mScreenSizeWidth + "," + mScreenSizeHeight);
            }
        }

        private int getStatusBarHeight() {
            if (mStatusBarHeight > 0) {
                return mStatusBarHeight;
            }

            int result = 0;
            int resourceId = mContext.getResources().getIdentifier("status_bar_height", "dimen", "android");
            if (resourceId > 0) {
                result = mContext.getResources().getDimensionPixelSize(resourceId);
            }
            mStatusBarHeight = result;
            return result;
        }

        public void setDrawable(boolean drawable) {
            mDrawable = drawable;
        }
    }

    public static Builder get(Context context) {
        return new Builder(context);
    }

    private Runnable hideRunnable = new Runnable() {
        @Override
        public void run() {
            hide();
        }
    };

    public void show() {
        if (isShow) return;
        try {
            mBuilder.initTN();
            mBuilder.show.invoke(mBuilder.mTN);
            mBuilder.mRootView.post(new Runnable() {
                @Override
                public void run() {
                    mBuilder.restorePosition();
                    isShow = true;
                }
            });

        } catch (Exception e) {
            e.printStackTrace();
        }
        //判断duration，如果大于#DURATION_ALWAYS 则设置消失时间

        if (mBuilder.mDuration > DURATION_ALWAYS) {
            mBuilder.mRootView.postDelayed(hideRunnable, mBuilder.mDuration);
        }
    }

    public final boolean isShowing() {
        return isShow;
    }

    public final void hide() {
        if (!isShow) return;
        mBuilder.cancelFreeFall();
        mBuilder.saveCurPosition();
        try {
            mBuilder.hide.invoke(mBuilder.mTN);
        } catch (Exception e) {
            e.printStackTrace();
        }
        isShow = false;
    }

    public final void toggle(){
        if (isShowing()){
            hide();
        }else {
            show();
        }
    }

    public FlowView setAlpha(float alpha) {
        mBuilder.mAlpha = alpha;
        return this;
    }

    public FlowView setFlingType(@FlyingType int type) {
        mBuilder.mFlingType = type;
        return this;
    }

    public int getFlingType(){
        return mBuilder.mFlingType;
    }

    public final View getRootView() {
        return mBuilder.toast.getView();
    }

    public final View getView() {
        return mBuilder.mView;
    }

    public boolean requestRefresh() {
        if (isShowing()) {
            mBuilder.requestRefresh();
            return true;
        }
        return false;
    }

    public void enableInterceptTouch(boolean enable){
        mBuilder.mInterceptTouch = enable;
    }

    /**
     * 设置是否允许拖拽
     *
     * @param drawable
     */
    public void setDrawable(boolean drawable) {
        mBuilder.setDrawable(drawable);
    }
}
